/*
 * Copyright (c) 2022 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
 import java.util.ArrayList;
 import java.util.Random;
 class Propertyaccess2 {
    public static long timeInUs() {
        return System.nanoTime();
    }

    // 多态
    public static class Base {
        public int baseNum = 1;
        Base (int num){
            this.baseNum = num;
        }
        public int compute() {
            return this.baseNum;
        }
    }

    public static class DeriveDouble extends Base {
        DeriveDouble(int num){
            super(num);
        }
        public int compute() {
            return this.baseNum * 2;
        }
    }

    public static class DerivedTripple extends Base {
        DerivedTripple(int num) {
            super(num);
        }
        public int compute()  {
            return this.baseNum * 3;
        }
    }


    public static void polymorphism() {
        int count = 100000;
        ArrayList<Integer> result = new ArrayList<>();
        ArrayList<Base> result2 = new ArrayList<>();
        ArrayList<Base> result3 = new ArrayList<>();
        for (int i = 0; i < count; i++) {
            result.add(i);
            result2.add(new DeriveDouble(i));
            result3.add(new DerivedTripple(i));
        }
        long start = timeInUs();
        for (int i = 0; i < count; i++) {
            if (result.get(i) == i) {
                result2.get(i).baseNum = result2.get(i).compute();
                result3.get(i).baseNum = result3.get(i).compute();
            }
        }
        long end = timeInUs();
        boolean res = true;
        for (int i = 0; i < count; i++) {
            if (result2.get(i).baseNum != i * 2 || result3.get(i).baseNum != i * 3) {
                res = false;
            }
        }
        if (!res) {
            System.out.println("result is wrong");
        }
        System.out.println("res: " + res);
        long time = (end - start);
        System.out.println("Property Access - Polymorphism:" + time / 1_000_000.0 + "ms");

    }

    // 单态
    public static class Square {
        public double length;
        public double width;
        Square(double length, double width) {
            this.length = length;
            this.width = width;
        }
    }

    public static Square[] generateFakeRandomSquare() {
        Square[] resource = new Square[10];
        for (int i = 0; i < 10; i++) {
            double random1 = Math.random() * 10 + 1;
            double random2 = Math.random() * 10 + 1;
            resource[i] = new Square(random1, random2);
        }
        return resource;
    }

    public static double singleICClass() {
        Square[] container = generateFakeRandomSquare();
        int count = 1000000;
        int arraySize = 3;
        double[] length_res = {0, 0, 0};
        double[] width_res = {0, 0, 0};
        long start = timeInUs();
        for (int i = 0; i < count; i++) {
            for (int j = 0; j < container.length; j++){
                Square thisBox = container[j];
                length_res[i % arraySize] += thisBox.length;
                width_res[i % arraySize] += thisBox.width;
            }
        }
        long end = timeInUs();
        double res = 0;
        for (int j = 0; j < arraySize; j++) {
            res += length_res[j] + width_res[j];
        }
        System.out.println("res: " + res);
        long time = end - start;
        System.out.println("Property Access - SingleICClass:" + time / 1_000_000.0 + "ms");
        return time;
    }



// 不可扩展属性
    public static class Person {
        public String name = "";
        public int age = 0;
        Person(String name, int age) {
            this.name = name;
            this.age = age;
        }

        public boolean equal(Person cmp) {
            return this.name.equals(cmp.name) && this.age == cmp.age;
        }
    }

    public static class Student extends Person {
        public String university = "";
        Student(String name, int age, String university) {
            super(name, age);
            this.university = university;
        }
    }

    public static Person[] generateFakeRandomPersons() {
        Person person1 = new Person("John", (int) (Math.random() * 50 + 1));
        Person person2 = new Person("John", (int) (Math.random() * 50 + 1));
        Person person3 = new Person("John", (int) (Math.random() * 50 + 1));
        Person[] resources = {person1, person2, person3};
        return resources;
    }

    public static double noneExtension() {
        Person person1 = new Person("John", 12);
        Person person2 = new Person("John", 13);
        Person person3 = new Person("John", 14);
        Person[] resourcesPerson = {person1, person2, person3};
        Student student1 = new Student("John", 21, "UNY");
        int count = 100000;
        int arraySize = 3;
        int[] res = {0, 0, 0};
        Person test = person1;
        long start = timeInUs();
        for (int i = 0; i < count; i++) {
            Person a = resourcesPerson[i % arraySize];
            if (a.equal(person1)) {
                Person b = resourcesPerson[(i + 1) % arraySize];
                if (b.equal(person2)) {
                    Person c = resourcesPerson[(i + 2) % arraySize];
                    if (c.equal(person3)) {
                        test = c;
                    }
                }
            }
            if (i % arraySize == 0 && test.equal(person3)) {
                res[i % 3] += 1;
                test = person1;
            }
            if (i % arraySize == 0 && test.equal(person1)) {
                res[i % 3] += 1;
                test = person2;
            }
        }
        long end = timeInUs();
        int sum = 0;
        for (int j = 0; j < arraySize; j++) {
            sum += res[j];
        }
        System.out.println("sum: " + sum);
        double time = (end - start);
        System.out.println("Property Access - NoneExtension:" + time / 1_000_000.0 + "ms");
        return time;
    }





    public static class MyObject {
        public int num;
        public int age;
        MyObject(int num, int age) {
            this.num = num;
            this.age = age;
        }
        public int getGetter() {
            return this.num;
        }
        public void setSetter(int n) {
            this.age = n;
        }
    }

    public static int[] generateNumArray(int count) {
        int[] numArray = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
        for (int i = 0; i < count; i++) {
            numArray[i] = i;
        }
        return numArray;
    }

    public static MyObject[] generateResultArray(int count) {
        MyObject[] result = new MyObject[10];
        for (int i = 0; i < count; i++) {
            result[i] = new MyObject(i, i);
        }
        return result;
    }

    public static MyObject[] getterSetterTest() {
        int count = 10000000;
        MyObject[] result = generateResultArray(10);
        int[] numArray = generateNumArray(10);

        long start = timeInUs();
        int resLen = result.length;
        for (int i = 0; i < count; i++) {
            int index = i % resLen;
            MyObject ret = result[index];
            if (ret.getGetter() == numArray[index]) {
                ret.setSetter(index);
            }
        }
        long end = timeInUs();
        double time = (end - start);
        System.out.println("Property Access - GetterSetterTest:" + time / 1_000_000.0 + " ms");
        return result;
    }
    public static void otherFunc() {
        MyObject[] result = getterSetterTest();
        int[] numArray = generateNumArray(result.length);
        int sum = 0;
        boolean res = true;
        for (int i = 0; i < result.length; i++) {
            sum += result[i].num;
            if (result[i].age != numArray[i]) {
                res = false;
                break;
            }
        }
        System.out.println("res: " + res);
        //System.out.println("sum: " + sum);


	}

// All Number object
    public static class Grades {
        public double math = 0.0;
        public double english = 0.0;
        public double physics = 0.0;
        public double chemistry = 0.0;
        Grades(double math, double english, double physics, double chemistry) {
            this.math = math;
            this.english = english;
            this.physics = physics;
            this.chemistry = chemistry;
        }
    }

    public static Grades[] generateFakeRandomGrades() {
        Grades[] resource = new Grades[10];
        for (int i = 0; i < 10; i++) {
            double random1 = Math.random() * 100.0 + 1.0;
            double random2 = Math.random() * 100.0 + 1.0;
            double random3 = Math.random() * 100.0 + 1.0;
            double random4 = Math.random() * 100.0 + 1.0;
            resource[i] = new Grades(random1, random2, random3, random4);
        }
        return resource;
    }

    public static double numberObject() {
        int count = 1000000;
        int arraySize = 2;
        Grades[] myGrades = generateFakeRandomGrades();
        double[] res = {0, 0, 0};
        long start = timeInUs();
        int gradesLength = myGrades.length - 1;
        for (int i = 0; i < count; i++) {
            double ret = res[i & arraySize];
            Grades grades = myGrades[i & gradesLength];
            ret += grades.chemistry;
            ret += grades.english;
            ret += grades.math;
            ret += grades.physics;
            res[i & arraySize] = ret;
        }
        long end = timeInUs();
        double sum = 0;
        for (int j = 0; j < arraySize; j++) {
            sum += res[j];
        }
        System.out.println("sum: " + sum);
        double time = (end - start);
        System.out.println("Property Access - NumberObject:" + time / 1_000_000.0 + " ms");
        return time;
    }
}
/*
 * @State
 * @Tags Jetstream2
 */
class Benchmark {
    public static void main(String[] args) {
        runPropertyacess();
    }
    /*
     *@Benchmark
     */
    public static void runPropertyacess(){
        Propertyaccess2.polymorphism();
        Propertyaccess2.singleICClass();
        Propertyaccess2.noneExtension();
        Propertyaccess2.otherFunc();
        Propertyaccess2.numberObject();
    }
}
